<!--
 * @Author: 王俊杰 “251082517@qq.com”
 * @Date: 2024-04-10 09:17:42
 * @LastEditors: 王俊杰 “251082517@qq.com”
 * @LastEditTime: 2024-04-10 17:41:45
 * @FilePath: /ai-h5/src/pages/date/index.vue
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
  <div class="calendar">
    <!-- 顶部 -->
    <div class="header">
      {{ selectData.year }}年{{ selectData.month }}月{{ selectData.day }}日
    </div>
    <!-- 星期 -->
    <div class="week-area">
      <div class="week-item" v-for="(item, index) in weekArr" :key="index">
        <text class="week-font calendar-item">{{ item }}</text>
      </div>
    </div>
    <!-- 每天 -->
    <div
      ref="calendarRef"
      class="data-container"
      :style="{
        height: isWeekdiv
          ? `${itemHeight + touchAreaHeight}px`
          : `${lineNum * itemHeight + touchAreaHeight}px`,
        transitionDuration: `${needHeightAnimation ? transitionDuration : 0}s`
      }"
      @touchstart="touchStart"
      @touchmove.stop.prevent="touchMove"
      @touchend="touchEnd"
    >
      <div
        class="month-area"
        :style="{
          transform: `translateX(${-(translateIndex + 1) * 100}%)`,
          transitionDuration: `${needAnimation ? transitionDuration : 0}s`
        }"
      >
        <div
          class="banner-area"
          :style="{
            transform: `translateY(${offsetY}px)`,
            transitionDuration: `${needHeightAnimation ? transitionDuration : 0}s`
          }"
        >
          <div
            v-for="(monthItem, monthIndex) in allDataArr"
            :key="monthIndex"
            class="data-area"
            :style="{
              transform: `translateX(
                ${(translateIndex + isTouching ? touch.x : 0) * 100}%
              )`,
              transitionDuration: `${isTouching ? 0 : transitionDuration}s`
            }"
          >
            <div
              v-for="(item, index) in monthItem"
              :key="index"
              :class="[
                'data-item',
                { 'selected-c': item.isSelected },
                { 'other-item': item.type !== 'normal' && !isWeekdiv }
              ]"
              :style="`height: ${itemHeight}px`"
              @click="checkoutDate(item)"
            >
              <span class="data-font calendar-item">{{ item.day }}</span>
            </div>
          </div>
        </div>
      </div>
    </div>
    <!-- 底部 -->
    <div
      class="touch-area"
      :style="`height: ${touchAreaHeight}px; padding-top: ${touchAreaPadding}px`"
    >
      <div
        class="touch-container"
        :style="`height:${touchAreaHeight - touchAreaPadding}px`"
      >
        <div class="touch-item"></div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { reactive, ref, watch, onMounted } from 'vue'

// 星期数据
const weekArr = reactive(['日', '一', '二', '三', '四', '五', '六'])
// 可视区域数据
const dataArr = reactive([])
// 轮播数组
const allDataArr = reactive([])
// 选中日期信息 -> year, month, day
const selectData = reactive({})
// 是否点选当前月份信息
const isSelectedCurrentDate = ref(false)
// 轮播所在位置
const translateIndex = ref(0)
// 动画持续时长
const transitionDuration = ref(0.3)
// 左右滑动是否需要动画
const needAnimation = ref(true)
// 是否为 touch 状态
const isTouching = ref(false)
// 初始滑动 x 的值
const touchStartPositionX = ref(null)
// 初始滑动 y 的值
const touchStartPositionY = ref(null)
// 本次 touch 事件，横向，纵向滑动的距离
const touch = reactive({
  x: 0,
  y: 0
})
// 周视图还是月视图
const isWeekdiv = ref(false)
// 子元素行高
const itemHeight = ref(50)
// 高度变化是否需要动画
const needHeightAnimation = ref(false)
// 周视图 y 轴偏移量
const offsetY = ref(0)
// 当前视图总行数
const lineNum = ref(0)
// 周视图 前一周数据
const lastWeek = reactive([])
// 周视图 后一周数据
const nextWeek = reactive([])
// 是否延迟 (动画结束在处理数据)
const isDelay = ref(true)
// 底部区域高度
const touchAreaHeight = ref(40)
// 底部区域 padding
const touchAreaPadding = ref(10)
// 点击元素(去除周视图切换月份时的动画延时)
const isClicked = ref(false)

watch(
  dataArr,
  (val) => {
    changeAllData(val)
  },
  {
    deep: true
  }
)

// watch(isWeekdiv, (val) => {
//   if (!val) {
//     isSelectedCurrentDate.value = false
//     changeAllData(dataArr)
//   }
// })

// 更新轮播数组
const changeAllData = (val) => {
  if (isSelectedCurrentDate.value && isWeekdiv.value) return

  const preDate = getPreMonth()
  const preDataArr = getMonthData(preDate, true)
  const nextDate = getNextMonth()
  const nextDataArr = getMonthData(nextDate, true)

  if (isWeekdiv.value) {
    const sliceStart = dealWeekdivSliceStart()
    preDataArr.splice(sliceStart, 7, ...lastWeek)
    nextDataArr.splice(sliceStart, 7, ...nextWeek)
  }

  const delayHandle = (isDay) => {
    allDataArr.length = 0
    allDataArr.push(preDataArr, val, nextDataArr)

    console.log(allDataArr, 9998)
    needAnimation.value = false
    translateIndex.value = 0
    if (isDay) {
      isDelay.value = false
    }
  }
  if (isDelay.value) {
    delayHandle(isDelay.value)
    return
  }
  setTimeout(
    () => {
      delayHandle()
    },
    isClicked.value && isWeekdiv.value ? 0 : transitionDuration.value * 1000
  )
}

// 获取当前日期
const getCurrentDate = () => {
  Object.assign(selectData, {
    year: new Date().getFullYear(),
    month: new Date().getMonth() + 1,
    day: new Date().getDate()
  })
}

// 获取指定月份数据
const getMonthData = (date, unSelected = false) => {
  const { year, month, day } = date
  let dataArr = []
  let daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
  if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
    daysInMonth[1] = 29
  }
  const monthStartWeekDay = new Date(year, month - 1, 1).getDay()
  const monthEndWeekDay = new Date(year, month, 1).getDay() || 7

  const preInfo = getPreMonth(date)
  const nextInfo = getNextMonth()

  for (let i = 0; i < monthStartWeekDay; i++) {
    let preObj = {
      type: 'pre',
      day: daysInMonth[preInfo.month - 1] - (monthStartWeekDay - i - 1),
      month: preInfo.month,
      year: preInfo.year
    }
    dataArr.push(preObj)
  }
  for (let i = 0; i < daysInMonth[month - 1]; i++) {
    let itemObj = {
      type: 'normal',
      day: i + 1,
      month,
      year,
      isSelected: day === i + 1 && !unSelected
    }
    dataArr.push(itemObj)
  }
  for (let i = 0; i < 7 - monthEndWeekDay; i++) {
    let nextObj = {
      type: 'next',
      day: i + 1,
      month: nextInfo.month,
      year: nextInfo.year
    }
    dataArr.push(nextObj)
  }
  return dataArr
}

// 点击选择切换日期
const checkoutDate = (selectData) => {
  isSelectedCurrentDate.value = true
  isClicked.value = true

  if (isWeekdiv.value && selectData.type !== 'normal') {
    needAnimation.value = false
    needHeightAnimation.value = false
  }

  if (selectData.type === 'next') {
    translateIndex.value += 1
    dealMonthData('NEXT_MONTH', selectData.day)
    return
  }

  if (selectData.type === 'pre') {
    translateIndex.value -= 1
    dealMonthData('PER_MONTH', selectData.day)
    return
  }

  Object.assign(selectData, { day: selectData.day })
  const oldSelectIndex = dataArr.findIndex(
    (item) => item.isSelected && item.type === 'normal'
  )
  const newSelectIndex = dataArr.findIndex(
    (item) => item.day === selectData.day && item.type === 'normal'
  )

  if (dataArr[oldSelectIndex]) {
    dataArr[oldSelectIndex].isSelected = false
  }
  if (dataArr[newSelectIndex]) {
    dataArr[newSelectIndex].isSelected = true
  }
}

// 获取前(后)一个月的年月日信息
const getPreMonth = (date, appointDay = 1) => {
  let { year, month } = date || selectData
  if (month === 1) {
    year -= 1
    month = 12
  } else {
    month -= 1
  }
  return { year, month, day: appointDay }
}
const getNextMonth = (appointDay = 1) => {
  let { year, month } = selectData
  if (month === 12) {
    year += 1
    month = 1
  } else {
    month += 1
  }
  return { year, month, day: appointDay }
}

// 切换上(下)一月
const handlePreMonth = () => {
  dealMonthData('PRE_MONTH')
}
const handleNextMonth = () => {
  dealMonthData('NEXT_MONTH')
}

// 处理月份数据
const dealMonthData = (type, appointDay = 1) => {
  isSelectedCurrentDate.value = false
  switch (type) {
    case 'PRE_MONTH':
      Object.assign(selectData, getPreMonth('', appointDay))
      break
    case 'NEXT_MONTH':
      Object.assign(selectData, getNextMonth(appointDay))
    default:
      break
  }
  dataArr.length = 0
  dataArr.push(...getMonthData(selectData))
  lineNum.value = Math.ceil(dataArr.length / 7)
  console.log(dataArr, 888, lineNum.value)
}

// 处理今日
const checkoutCurrentDate = () => {
  isDelay.value = true
  getCurrentDate()
  dealMonthData()
}

// touch 事件
const calendarRef = ref(null)
const touchStart = (event) => {
  isTouching.value = true
  needAnimation.value = true
  isClicked.value = false

  touchStartPositionX.value = event.touches[0].clientX
  touchStartPositionY.value = event.touches[0].clientY
  Object.assign(touch, { x: 0 })
}
const touchMove = (event) => {
  const moveX = event.touches[0].clientX - touchStartPositionX.value
  const moveY = event.touches[0].clientY - touchStartPositionY.value

  if (Math.abs(moveX) > Math.abs(moveY)) {
    // 左右
    needHeightAnimation.value = false
    Object.assign(touch, { x: moveX / calendarRef.value.offsetWidth, y: 0 })
  } else {
    // 上下
    needHeightAnimation.value = true
    Object.assign(touch, {
      x: 0,
      y: moveY / calendarRef.value.offsetHeight
    })
  }
}
const touchEnd = () => {
  isTouching.value = false
  const { x, y } = touch

  // 月视图
  if (Math.abs(x) > Math.abs(y) && Math.abs(x) > 0.3) {
    if (x > 0) {
      // 左
      translateIndex.value -= 1
      isWeekdiv.value ? handlePreWeek() : handlePreMonth()
    } else if (x < 0) {
      // 右
      translateIndex.value += 1
      isWeekdiv.value ? handleNextWeek() : handleNextMonth()
    }
  }

  // 周视图
  if (
    Math.abs(y) > Math.abs(x) &&
    Math.abs(y * calendarRef.value.offsetHeight) > 50
  ) {
    if (y > 0) {
      // 下
      isWeekdiv.value = false
      offsetY.value = 0
    } else if (y < 0) {
      // 上
      isWeekdiv.value = true
      dealWeekViewData()
    }
  }

  Object.assign(touch, { x: 0, y: 0 })
}

// 周视图的位置信息
const getInfoOfWeekdiv = (selectedIndex, length) => {
  const indexOfLine = Math.ceil((selectedIndex + 1) / 7)
  const totalLine = Math.ceil(length / 7)
  const sliceStart = (indexOfLine - 1) * 7
  const sliceEnd = sliceStart + 7

  return { indexOfLine, totalLine, sliceStart, sliceEnd }
}

// 生成前(后)一周数据
const dealWeekdivSliceStart = () => {
  const selectedIndex = dataArr.findIndex((item) => item.isSelected)
  const { indexOfLine, totalLine, sliceStart, sliceEnd } = getInfoOfWeekdiv(
    selectedIndex,
    dataArr.length
  )
  offsetY.value = -((indexOfLine - 1) * itemHeight.value)

  // 前一周数据
  lastWeek.length = 0
  if (indexOfLine === 1) {
    const perInfo = getPreMonth()
    const preDataArr = getMonthData(preInfo, true)
    const preDay = dataArr[0].day - 1 || preDataArr[preDataArr.length - 1].day
    const preIndex = preDataArr.findIndex(
      (item) => item.day === preDay && item.type === 'normal'
    )
    const { sliceStart: preSliceStart, sliceEnd: preSliceEnd } =
      getInfoOfWeekdiv(preIndex, preDataArr.length)
    lastWeek.push(...preDataArr.slice(preSliceStart, preSliceEnd))
  } else {
    lastWeek.push(...dataArr.slice(sliceStart - 7, sliceEnd - 7))
  }

  // 后一周数据
  nextWeek.length = 0
  if (indexOfLine >= totalLine) {
    const nextInfo = getNextMonth()
    const nextDataArr = getMonthData(nextInfo, true)
    const nextDay =
      dataArr[dataArr.length - 1].type === 'normal'
        ? 1
        : dataArr[dataArr.length - 1].day + 1
    const nextIndex = nextDataArr.findIndex((item) => item.day === nextDay)
    const { sliceStart: nextSliceStart, sliceEnd: nextSliceEnd } =
      getInfoOfWeekdiv(nextIndex, nextDataArr.length)
    nextWeek.push(...nextDataArr.slice(nextSliceStart, nextSliceEnd))
  } else {
    nextWeek.push(...dataArr.slice(sliceStart + 7, sliceEnd + 7))
  }
  return sliceStart
}

// 切换上(下)一周
const handlePreWeek = () => {
  dealWeekData('PRE_WEEK')
}
const handleNextWeek = () => {
  dealWeekData('NEXT_WEEK')
}

// 处理周数据
const dealWeekData = (type) => {
  console.log(type, 999)
  const { year, month, day } =
    type === 'PRE_WEEK'
      ? lastWeek.find((item) => item.type === 'normal')
      : nextWeek[0]
  Object.assign(selectData, {
    year,
    month,
    day
  })
  dataArr.push(...getMonthData(selectData))
  lineNum.value = Math.ceil(dataArr.length / 7)
  offsetY.value -= itemHeight.value
  dealWeekViewData()
}
// 处理上(下)一周数据
const dealWeekViewData = () => {
  const sliceStart = dealWeekdivSliceStart()
  allDataArr[0].splice(sliceStart, 7, ...lastWeek)
  allDataArr[2].splice(sliceStart, 7, ...nextWeek)
}

onMounted(() => {
  checkoutCurrentDate()
})
</script>

<style lang="scss" scoped>
.calendar {
  overflow-x: hidden;
  padding: 16px;

  .header {
    padding: 0 5px;
    font-size: 18px;
    font-weight: 500;
    color: #2b4450;
    line-height: 44px;
    margin: 0 calc((14.285% - 40px) / 2 + 6px);
  }

  .week-area {
    width: 100%;
    display: flex;

    .week-item {
      height: 45px;
      flex: 0 0 14.285%;
      display: flex;
      align-items: center;
      justify-content: center;

      .week-font {
        font-size: 15px;
        color: #2b4450;
        font-size: 500;
      }
    }
  }
  .calendar-item {
    display: block;
    width: 40px;
    height: 40px;
    text-align: center;
    line-height: 40px;
  }
  .data-container {
    overflow: hidden;
    position: relative;

    .banner-area {
      width: 300%;
      display: flex;

      .data-area {
        width: 100%;
        height: 100%;
        display: flex;
        flex-flow: row wrap;

        .data-item {
          flex: 0 0 14.285%;
          display: flex;
          align-items: center;
          justify-content: center;

          .data-font {
            color: #2b4450;
            font-size: 18px;
            font-weight: 400;
          }
          &.selected-c {
            .data-font {
              background: #2b4450;
              border-radius: 50%;
              color: #fff;
            }
          }
        }
        .other-item {
          .data-font {
            color: #cccccc;
          }
        }
      }
    }
  }
  .touch-area {
    width: 100%;
    box-sizing: border-box;
    background: #ffffff;
    position: absolute;
    left: 0;
    bottom: 0;
  }
  .touch-container {
    width: 100%;
    box-sizing: border-box;
    border-top: 0.5px solid #eee;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .touch-item {
    width: 40px;
    height: 5px;
    background: #2b4450;
    border-radius: 100px;
    opacity: 0.6;
  }
}
</style>
